#include <nice2d/application.h>
#include <nice2d/components/scenecamera.h>
#include <nice2d/string.h>

IMPLEMENT_OBJECT(SceneCamera, Component)

class SceneCameraData
{
public:
    SceneCameraData(SceneCamera &inputCamera):
        camera(inputCamera)
    {
    }

    void onTargetPositionChanged(const sf::Vector2f &newPosition)
    {
        camera.setCenter(newPosition);
    }

    SceneCamera &camera;
    sf::View view;
    bool dirty = false;
    sf::FloatRect globalArea;
    std::weak_ptr<Entity> target;
    std::weak_ptr<Scene> scene;
};

SceneCamera::SceneCamera():
    data(new SceneCameraData(*this))
{
}

SceneCamera::~SceneCamera()
{
}

void SceneCamera::setScene(ScenePointer scene)
{
    data->scene = scene;
}

std::weak_ptr<Scene> SceneCamera::getScene()
{
    return data->scene;
}

void SceneCamera::setViewSize(const sf::Vector2f &size)
{
    data->view.setSize(size);
    data->dirty = true;
}

sf::View SceneCamera::getView() const
{
    return data->view;
}

void SceneCamera::setWindowArea(const sf::FloatRect &area)
{
    data->view.reset(area);
    if (data->globalArea.width < 1.0f)
        data->globalArea = area;
    data->dirty = true;
}

void SceneCamera::setGlobalArea(const sf::FloatRect &area)
{
    data->globalArea = area;
}

void SceneCamera::setCenter(const sf::Vector2f &center)
{
    sf::Vector2f newCenter(center);
    auto size = data->view.getSize();

    if (newCenter.x - size.x * 0.5f < data->globalArea.left) {
        newCenter.x = size.x * 0.5f + data->globalArea.left;
    }
    else if(newCenter.x + size.x*0.5f > data->globalArea.left + data->globalArea.width)
        newCenter.x = data->globalArea.left + data->globalArea.width - size.x*0.5f;

    if (newCenter.y - size.y * 0.5f < data->globalArea.top) {
        newCenter.y = size.y * 0.5f + data->globalArea.top;
    }
    else if(newCenter.y + size.y*0.5f > data->globalArea.top + data->globalArea.height)
        newCenter.y = data->globalArea.top + data->globalArea.height - size.y*0.5f;

    data->view.setCenter(newCenter);
    data->dirty = true;
}

void SceneCamera::setTarget(EntityPointer target)
{
    if (target) {
        data->target = target;
        target->positionChanged.connect(std::bind(&SceneCameraData::onTargetPositionChanged, data.get(),
                                                  std::placeholders::_1));
    }
}

void SceneCamera::setViewport(const sf::FloatRect &area)
{
    data->view.setViewport(area);
    data->dirty = true;
}

void SceneCamera::setRotate(float angle)
{
    data->view.setRotation(angle);
    data->dirty = true;
}

void SceneCamera::update(float deltaTime)
{
    (void)deltaTime;
    if (data->dirty) {
        data->dirty = false;

        Application::getInstance()->getWindow()->setView(data->view);

        auto center = getView().getCenter();
        auto size = getView().getSize();
        auto leftTop = center - size * 0.5f;
        leftTopChanged(leftTop);
    }
}